/*
 *	CODESPACE ++
 *	A real-time syntax highlighting javascript editor
 *
 *	Copyright (C) 2008 Francois Botman, <Franchie.brightspark@gmail.com>
 *
 *	This script is licenced under: Creative Commons Attribution-Noncommercial-Share Alike 3.0
 *	(see http://creativecommons.org/licenses/by-nc-sa/3.0/).
 *	It bascially means that it can be modified and shared, provided it is
 *	clearly attributed, and can be used in personnal/charity projects for free!
 *	(Commercial use requires prior negotiation with the author).
 *	In any case, should anything go wrong, I cannot be held responsible!
 *
 *	Based on an original idea by Fernando M.A.d.S.,  http://codepress.org/
 *
 *	-----------
 *
 *	This file contains functions that handle syntax highlighting and caret movement via the mouse.
 *
 *	Syntax highlighting is done in the following way: it is a word-based engine, in that it is not possible to highlight individual letters of a word.
 *	Words are defined by a seriees of delimiters ("tokenizer"), so this still allows a certain degree of freedom.
 *	It is possible to apply rules to single words ('match'), or to a block ('start' and 'stop').
 *	Rules are easily propagated fom line to line, since the state of the engine is saved for each line. This allows syntax highlighting to be
 *	both rapidly updated and propagated to the following lines (such as in the case of a block opening in the middle of the editor).
 *
 *	Code folding is implemented in a similar manner. Simply create a block rule with the css style set to "folding", and the rest will be automatic.
 *
 *	Please not that each rule can set several styles, each being seperated by a space.
 *
 *	This file only handles:
 *		- row formatting into blocks
 *		- syntax highlighting
 *		- code folding
 *		- mouse caret movement
 */

/*
 * Here are some useful functions that are used later on
 */
var word_split_char = 		'\u0000';
var char_end_of_string =	'\u0002';
//var char_tab =				'\u0009';	// this is actually correct!




/*
 *	This structure will contain all the rules. 
 *	Other rules will be added in other files...
 */
Codespace.Language = {};

/*
 *	Also define a 'default' language, in case none is specified
 */
Codespace.Language['default'] = {
	/*
	 *	The tokenizer contains regexps to determine where a line should be split into words.
	 *	The default seperator is a space, so all different words should be seperated by spaces.
	 *	Since 'space' is also considered a word (a space must be inserted), a special character is used.
	 */
	tokenizer:	[
		/*{m: /(\ )/g, r: ' '+char_space+' '},
		{m: /(\t)/g, r: ' '+char_tab+' '},
		{m: /(\{|\[|\]|\})/g, r: ' $1 '},
		{m: /(\$\$?)/g, r: ' $1 '}*/
		{m: /(\s|\{|\[|\]|\}|\$\$?)/g, r: word_split_char+'$1'+word_split_char}
	],
	
	/*
	 *	Rules:
	 *	These rules determine what css class a word will have.
	 *	There are two forms: either just a word, or a word block.
	 *		- A word rule just modifies the style of the matching word
	 *		- A block rule is defined by a start and an end, and everything in between is applied the class.
	 *	The special character 'char_end_of_string' matches the end of a physical line.
	 *
	 *	Thus the format of a rule is the following:
	 *		- start:	rule that starts a block. If this is specified, there should usually be a stop
	 *		- stop:	rule that ends a block.
	 *		- match:	rule that matches a single word and is not propagated.
	 *		- css:		the name of the css style to apply.
	 */
	rules: [
		{start: null,	stop: null,								match: /\\.*/,	css: 't1'},
		{start: /\{/,	stop: /\}/,								match: null,	css: 't2 folding'},
		{start: /\[/,	stop: /\]/,								match: null,	css: 't3'},
		{start: /\%/,	stop: new RegExp(char_end_of_string),	match: null,	css: 't5'},
		{start: /\/\*/,	stop: /\*\//,							match: null,	css: 't5'},
		{start: null,	stop: null,								match: /this/i,	css: 't4'},
		{match: /(https?\:\/\/|https?\:\/\/www\.|www\.)(.*)\.[a-z][a-z]([a-z]?)/i,css: 'url'},
		{match: new RegExp(char_end_of_string),	css: 't3'}
	]
};





String.prototype.convertToHTML = function() {
	return this.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;')
			.replace(/\s/g, '&nbsp;')
			.replace(/\</g, '&lt;')
			.replace(/\>/g, '&gt;');
}






/*
 *	And this is the formatter function.
 *	It should not be instanciated, it should be called as is.
 *
 *	This function takes the table row in which to insert the data, the one before it, the previous highlight state (which is either
 *	the previous row, or the same row but during the last recursion), the text to insert, and the language.
 *
 *	It returns true if the style has changed, and the changes need to be propagated to the following lines, false otherwise
 */
Codespace.Formatter = function(obj, last_word, language)
{
	/*
	 *	This object contains the state of the highlighter span per span.
	 *	It is convenient, as it automatically clones the properties of the object used to build it.
	 */
	function highlighterState(clone) {
		if(clone) for(var a in clone) if(clone[a]) this[a] = clone[a];
	}

	var state = new highlighterState(obj ? obj.before : null);
	var style = '';
	var counter = 0;
	var child = 0;
	
	var rule = Codespace.Language[language] || Codespace.Language['default'];
	var txt = obj.text;
	if(last_word) txt += char_end_of_string;
	
	style = '';
	
	// Apply all rules
	for(var y=0; y<rule.rules.length; y++) {
	
		// Word-matching, change only current word
		if(rule.rules[y].match && txt.match(rule.rules[y].match)) style += rule.rules[y].css + ' ';
		
		// Block matching: propagate changes:
		// Begin css block
		if(rule.rules[y].start && txt.match(rule.rules[y].start)) {
			style += rule.rules[y].css + ' ';
			var s = rule.rules[y].css.split(' ');
			// Some rules are actually a combination of options!
			for(var q=0, l=s.length; q<l; q++) state[s[q]] = (state[s[q]]) ? (state[s[q]]+1) : 1;
		}
		// End css block
		if(rule.rules[y].stop && txt.match(rule.rules[y].stop)) {
			var s = rule.rules[y].css.split(' ');
			for(var q=0, l=s.length; q<l; q++) {
				if(state[s[q]] && state[s[q]]>0) style += s[q] + ' ';
				state[s[q]] = (state[s[q]] && state[s[q]]>1) ? (state[s[q]]-1) : null;
			}
		}
	}

	// And apply all remaining block styles
	for(var y in state) if(state[y]>0) style += y+' ';
	
	obj.className = 'codespace '+style;
	obj.innerHTML = obj.text.convertToHTML();
	
	//console.log('Formatting "'+txt+'"');
	//console.log(state);
	
	// And return whether the styles were changed compared to the given reference. Only once the changes observed is the state updated!
	if(!obj.highlightState) {obj.highlightState = state;return true;}
	for(var i in state) if(obj.highlightState[i] != state[i]) {obj.highlightState = state;return true;}
	for(var i in obj.highlightState) if(obj.highlightState[i] != state[i]) {obj.highlightState = state;return true;}
	
	// Unchanged since last pass...
	obj.highlightState = state;
	return false;

}
Codespace.Formatter.split = function(txt, language)
{
	var rule = Codespace.Language[language] || Codespace.Language['default'];
	
	// Ensure the text is actually valid!
	if(!txt) return [null];
	
	// Firstly, replace all tokenizers by their equivalents (to split the string into words)
	for(var i=0; i<rule.tokenizer.length; i++) txt = txt.replace(rule.tokenizer[i].m, rule.tokenizer[i].r);
	
	// Then split into distinct words, and process each non-null segment (ie: that contain useful information)
	return txt.split(word_split_char);
}




/*
 *	This implements the code folding.
 *	It basically consists in determining the indentation value, and placing an adequate icon when that value changes.
 *	Oh, and also managing the actual line hiding!
 */
Codespace.Folder = function(self, previous, row)
{
	var b4 = (previous && previous.state ? previous.state['folding'] : 0) || 0;
	var after = (self && self.state ? self.state['folding'] : 0) || 0;
	if(b4 < after) row.innerHTML =  '+';
	else if(b4 > after) row.innerHTML =  (after>0) ? '=' : '-';
	else if(after>0) row.innerHTML = '|';
	else row.innerHTML =  '';
}


